package cn.enited.devtools.apiaggregator.api.analysis;

import cn.enited.devtools.apiaggregator.api.vo.RemoteService;
import cn.enited.devtools.apiaggregator.consts.ErrorCode;
import cn.enited.devtools.apiaggregator.exception.BizException;
import cn.hutool.core.util.StrUtil;
import lombok.Getter;

import java.util.*;

/**
 * 有向图
 */
public class Digraph {

    /**
     * 顶点的id集合
     */
    @Getter
    private Set<String> vertices;
    /**
     * 弧的数量
     */
    @Getter
    private int edges;

    /**
     * 顶点及邻接顶点集合
     * key: 顶点id
     * value: 邻接顶点id集合
     */
    @Getter
    private Map<String, LinkedList<String>> adjacentVertices;

    /**
     * 入度计数器
     * key: 弧头(顶点)id
     * value: 入度
     */
    private Map<String, Integer> indegreeCounter;

    public Digraph() {
        this.vertices = new HashSet<>();
        this.adjacentVertices = new HashMap<>();
        this.indegreeCounter = new HashMap<>();
    }

    public Digraph(RemoteService service) {
        this();
        String tail = service.getId();
        service.getDependServiceIds().forEach(head -> addEdge(tail, head));
    }

    /**
     * Adds the directed edge v→w to this digraph.
     *
     * @param  tail the tail vertex
     * @param  head the head vertex
     * @throws IllegalArgumentException unless both {@code 0 <= v < V} and {@code 0 <= w < V}
     */
    public void addEdge(String tail, String head) {
        if (StrUtil.isBlank(tail)) {
            throw new BizException(ErrorCode.INVALID_DATA.code(), StrUtil.format("弧尾不能为空"));
        }
        if (StrUtil.isBlank(head)) {
            throw new BizException(ErrorCode.INVALID_DATA.code(), StrUtil.format("弧头不能为空"));
        }
        if (tail.equalsIgnoreCase(head)) {
            throw new BizException(ErrorCode.INVALID_DATA.code(),
                    StrUtil.format("service[{}]依赖了自己", tail));
        }
        if (adjacentVertices.containsKey(tail) && adjacentVertices.get(tail).contains(head) &&
        adjacentVertices.containsKey(head) && adjacentVertices.get(head).contains(tail)) {
            throw new BizException(ErrorCode.INVALID_DATA.code(),
                    StrUtil.format("service[{}]和[{}]之间存在直接的循环依赖", tail, head));
        }
        if (!adjacentVertices.containsKey(tail)) {
            adjacentVertices.put(tail, new LinkedList<>());
        }
        adjacentVertices.get(tail).add(head);
        this.vertices.add(tail);
        this.vertices.add(head);
        this.edges++;
        int indegree = this.indegreeCounter.getOrDefault(head, 0);
        this.indegreeCounter.put(head, ++indegree);
    }

    /**
     * 所有顶点的数量
     * @return
     */
    public int vertexSize() {
        return this.vertices.size();
    }

    private void validateVertex(String v) {
        if (!this.adjacentVertices.containsKey(v)) {
            throw new IllegalArgumentException(StrUtil.format("顶点[{}]不存在", v));
        }
    }

    /**
     * 顶点的出度 {@code v}.
     * This is known as the <em>outdegree</em> of vertex {@code v}.
     *
     * @param  v the vertex
     * @return the outdegree of vertex {@code v}
     * @throws IllegalArgumentException unless {@code 0 <= v < V}
     */
    public int outdegree(String v) {
        validateVertex(v);
        return this.adjacentVertices.get(v).size();
    }

    /**
     * 顶点的入度 {@code v}.
     * This is known as the <em>indegree</em> of vertex {@code v}.
     *
     * @param  v the vertex
     * @return the indegree of vertex {@code v}
     * @throws IllegalArgumentException unless {@code 0 <= v < V}
     */
    public int indegree(String v) {
        return this.indegreeCounter.getOrDefault(v, 0);
    }

    /**
     * 顶点{@code v}的邻接顶点(弧头)集合
     *
     * @param  v the vertex
     * @return the vertices adjacent from vertex {@code v} in this digraph, as an iterable
     * @throws IllegalArgumentException unless {@code 0 <= v < V}
     */
    public List<String> getAdjacencyList(String v) {
        validateVertex(v);
        return this.adjacentVertices.get(v);
    }

    public void release() {
        this.vertices.clear();
        this.vertices = null;
        this.adjacentVertices.values().forEach(List::clear);
        this.adjacentVertices.clear();
        this.adjacentVertices = null;
    }
}
